#include "module.h"
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

#include "token.h"
#include "ast.h"
#include "base.h"
#include "option.h"
#include "expr.h"
#include "stmt.h"
#include "buffer.h"
#include "common.h"
#include <set>
#include <string>
#include <map>
#include <stack>
#include <vector>

static const char* print_key_string(const char* s)
{
    while (*s) {
        if (*s == '%') {
            s++;
            if (*s == '%') {
                putchar('%');
                s++;
                continue;
            } else {
                return s;
            }
        }
        if (*s == '\\') {
            switch (s[1]) {
            case '\'': putchar('\''); s++; break;
            case '\"': putchar('\"'); s++; break;
            case '\\': putchar('\\'); s++; break;
            case 'a': putchar('\a'); s++; break;
            case 'b': putchar('\b'); s++; break;
            case 'f': putchar('\f'); s++; break;
            case 'n': putchar('\n'); s++; break;
            case 'r': putchar('\r'); s++; break;
            case 't': putchar('\t'); s++; break;
            case 'v': putchar('\v'); s++; break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            {
                if (s[1] == 0) {
                    putchar(s[0]);
                    break;
                }
                s++;
                int n = 0;
                for (int i = 0; i < 3; ++i)
                {
                    if (*s >= '0' && *s <= '9') {
                        n = (n << 3) + *s - '0';
                        s++;
                    }
                }
                putchar(n);
                continue;
            }
            case 'x': {
                if (s[1] == 0 || s[2] == 0) {
                    putchar(s[0]);
                    break;
                }
                int n = 0;
                for (int i = 0; i < 2; ++i)
                {
                    char c = s[2+i];
                    if (c >= 'a' && c <= 'f') {
                        n = (n << 4) + (c - 'a') + 10;
                    } else if (c >= 'A' && c <= 'F') {
                        n = (n << 4) + (c - 'A') + 10;
                    } else if (isdigit(c)) {
                        n = (n << 4) + (c - '0');
                    } else {
                        putchar(*s);
                        continue;
                    }
                }
                s += 4;
                putchar(n);
                continue;
            }
            default: putchar(*s); break;
            }
        } else {
            putchar(*s);
        }
        s++;
    }
    return nullptr;
}

void do_intrinsic_print(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    int char_count = 0;
    if (env->local_count() == 0) {
        env->push_local({ atype_t::ANY_I8, char_count });
        return;
    }
    auto args = env->get_local(0);
    if (args.type == atype_t::ANY_STRING) {
        const char* s = args.s;
        int count = 1;
        char_count = (int)strlen(s);
        while ((s = print_key_string(s))) {
            if (count < env->local_count()) {
                --char_count;
                char_count += print_any(env->get_local(count));
            } else {
                putchar('%');
            }
            count++;
        }
        if (count < env->local_count()) {
            for (int i = count; i < env->local_count(); ++i) {
                if (i != 0) {
                    char_count += printf(", ");
                }
                char_count += print_any(env->get_local(i));
            }
            char_count += printf("\n");
        }
    } else {
        for (int i = 0; i < env->local_count(); ++i) {
            if (i != 0) {
                char_count += printf(", ");
            }
            char_count += print_any(env->get_local(i));
        }
    }
    int count = env->local_count();
    env->clear();
    env->push_local({ atype_t::ANY_I8, char_count });
    env->return_done = true;
}
void do_intrinsic_println(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    do_intrinsic_print(parent, env, arg, bind);
    any_t a = env->get_local(0);
    a.i8++;
    env->clear();
    env->push_local(a);
    printf("\n");
}
void do_intrinsic_array(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    any_t ret(atype_t::ANY_ARRAY, array_t::create(env->local_count()));
    ret.array->set_type(g_class_array);
    for (int i = 0; i < env->local_count(); ++i) {
        ret.array->set(i, env->get_local(i));
    }
    env->clear();
    env->push_local(ret);
    env->return_done = true;
}
void do_intrinsic_append(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    if (env->local_count() != 2) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "the function 'append' need too arguments, "
            "while %d is given\n", env->local_count());
        return;
    }
    auto array = env->get_local(0);
    if (array.type != atype_t::ANY_ARRAY && array.type != atype_t::ANY_STRING) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "try to append to a non array type\n");
        return;
    }
    if (array.type == atype_t::ANY_ARRAY) {
        if (!array.array->push(env->get_local(1))) {
            mark_line();
            do_error(&arg->loc, "fail to alloc memory\n");
            return;
        }
    }
    else {
        mark_line();
        assert(0);
    }
    env->clear();
    env->push_local(array);
    env->return_done = true;
}
void do_intrinsic_len(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    assert(env->local_count() == 1);
    auto arg = env->get_local(0);
    env->clear();
    if (arg.type == atype_t::ANY_STRING) {
        env->push_local({atype_t::ANY_I8, (int)strlen(arg.s)});
    } else if (arg.type == atype_t::ANY_ARRAY) {
        if (arg.array == nullptr) {
            env->push_local({ atype_t::ANY_I8, 0 });
        } else {
            env->push_local({ atype_t::ANY_I8, arg.array->get_count() });
        }
    } else if (arg.type == atype_t::ANY_DICT) {
        if (arg.dict == nullptr) {
            env->push_local({ atype_t::ANY_I8, 0 });
        } else {
            env->push_local({ atype_t::ANY_I8, arg.dict->get_count() });
        }
    } else {
        env->push_local({ atype_t::ANY_I8, 1 });
    }
    env->return_done = true;
}

void do_intrinsic_dict(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    any_t ret(atype_t::ANY_DICT, dict_t::create());
    ret.dict->set_type(g_class_dict);
    for (int i = 0; i < env->local_count(); ++i)
    {
        auto arg = env->get_local(i);
        if (arg.type != atype_t::ANY_PAIR) {
            mark_line();
            set_calc_error();
            do_error(&data->loc, "expect a key-value argument here: ");
            print_any(arg);
            return;
        }
        if (!ret.dict->insert(arg.pair->first, arg.pair->second)) {
            mark_line();
            set_calc_error();
            do_error(&data->loc, "failed to make dict");
            return;
        }
    }
    env->clear();
    env->push_local(ret);
    env->return_done = true;
}

void do_intrinsic_swap(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    assert(env->local_count() == 2);
    auto arg1 = env->get_local(0);
    auto arg2 = env->get_local(1);
    assert(arg1.type == atype_t::ANY_REF);
    assert(arg2.type == atype_t::ANY_REF);
    auto type = arg1.ref->type;
    arg1.ref->type = arg2.ref->type;
    arg2.ref->type = type;
    auto val = arg1.ref->i8;
    arg1.ref->i8 = arg2.ref->i8;
    arg2.ref->i8 = val;
    env->clear_local();
}

void do_intrinsic_typename(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    assert(env->local_count() >= 1);
    auto arg1 = env->get_local(0);
    env->clear();
    env->push_local({ atype_t::ANY_STRING, mstrdup(get_type_name(arg1.type))});
    env->return_done = true;
}

void do_intrinsic_has(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    any_t ret({ atype_t::ANY_BOOL, 0});
    assert(env->local_count() >= 2);
    auto arg1 = env->get_local(0);
    auto arg2 = env->get_local(1);
    env->clear();
    if (arg1.type == atype_t::ANY_ARRAY) {
        auto array = arg1.array;
        if (array == nullptr || array->get_count() == 0) {
            // do nothing
        } else {
            for (int i = 0; i < array->get_count(); ++i) {
                ret = equal_any(*array->get(i), arg2);
                if (ret.i8 != 0) {
                    break;
                }
            }
        }
    } else if (arg1.type == atype_t::ANY_DICT) {
        auto dict = arg1.dict;
        if (dict == nullptr || dict->get_count() == 0) {
            // do nothing
        } else {
            ret.i8 = dict->find(arg2) != nullptr;
        }
    } else if (arg1.type == atype_t::ANY_STRING) {
        auto s = arg1.s;
        if (s == nullptr || s[0] == 0) {
            // do nothing
        } else {
            if (arg2.type == atype_t::ANY_CHAR || arg2.type == atype_t::ANY_I8) {
                ret.i8 = strchr(s, (int)arg2.i8) != nullptr;
            } else if (arg2.type == atype_t::ANY_STRING) {
                if (arg2.s == nullptr)
                    ret.i8 = 0;
                else
                    ret.i8 = strstr(s, arg2.s) != nullptr;
            }
        }
    } else {
        mark_line();
        set_calc_error();
        do_error(&tokc.loc, "bug: call a 'has' method on unexpected type");
    }
    env->push_local(ret);
    env->return_done = true;
}

void do_intrinsic_split(env_t* parent, env_t* env, expr_t* data, void* bind)
{
    (void)data;
    any_t ret({ atype_t::ANY_BOOL, 0 });
    assert(env->local_count() >= 1);
    auto arg1 = env->get_local(0);
    if (arg1.type != atype_t::ANY_STRING) {
        env->clear();
        env->push_local(any_t(atype_t::ANY_NONE, 0));
        env->return_done = true;
        return;
    }
    char ss[2];
    const char* delim = nullptr;
    if (env->local_count() > 1) {
        auto arg2 = env->get_local(1);
        if (arg2.type == atype_t::ANY_STRING) {
            delim = arg2.s;
        }
        else if (arg2.type == atype_t::ANY_CHAR || arg2.type == atype_t::ANY_I8) {
            ss[0] = (char)arg2.i8;
            ss[1] = 0;
            delim = ss;
        }
        else {
            env->clear();
            env->push_local(any_t(atype_t::ANY_NONE, 0));
            env->return_done = true;
            return;
        }
    }
    else {
        delim = " \t\r\b\n";
    }
    env->clear();
    do_intrinsic_array(parent, env, data, bind);
    any_t array = env->get_local(0);
    auto copy = _strdup(arg1.s);
    auto split = strtok(copy, delim);
    while (split) {
        array.array->push(any_t(atype_t::ANY_STRING, mstrdup(split)));
        split = strtok(nullptr, delim);
    }
    free(copy);
}

void do_intrinsic_get_tokens(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    any_t array(atype_t::ANY_ARRAY, array_t::create(0));
    array.array->set_type(g_class_array);

    if (env->local_count() < 1) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "the function 'get_tokens' need a argument, "
            "while %d is given\n", env->local_count());
        return;
    }
    auto ss = env->get_local(0);
    if (ss.type != atype_t::ANY_STRING) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "try to get tokens from a non string type\n");
        return;
    }
    option_t option;
    int len = (int)strlen(ss.s);
    token_builder builder(ss.s, len, nullptr, &option);
    token_t tok;
    do {
        tok = builder.get_token();
        if (tok.kind == TK_NONE)
            break;
        array.array->push({ atype_t::ANY_STRING, new_auto_str(&tok) });
    } while (1);

    env->clear();
    env->push_local(array);
    env->return_done = true;
}

void do_intrinsic_isupper(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    any_t ret({ atype_t::ANY_BOOL, 0});
    if (env->local_count() != 1) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "the function 'isupper' need a argument, "
            "while %d is given\n", env->local_count());
        return;
    }
    auto ss = env->get_local(0);
    if (ss.type == atype_t::ANY_CHAR || ss.type == atype_t::ANY_I8) {
        ret.i8 = isupper((int)ss.i8);
    } else if (ss.type == atype_t::ANY_STRING) {
        int len = (int)strlen(ss.s);
        ret.i8 = 1;
        for (int i = 0; i < len; ++i) {
            if (!isupper(ss.s[i])) {
                ret.i8 = 0;
                break;
            }
        }
    } else {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "try to apply isupper on a non string type\n");
        return;
    } 
    env->clear();
    env->push_local(ret);
    env->return_done = true;
}

void do_intrinsic_islower(env_t* parent, env_t* env, expr_t* arg, void* bind)
{
    (void)arg;
    any_t ret({ atype_t::ANY_BOOL, 0});
    if (env->local_count() != 1) {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "the function 'islower' need a argument, "
            "while %d is given\n", env->local_count());
        return;
    }
    auto ss = env->get_local(0);
    if (ss.type == atype_t::ANY_CHAR || ss.type == atype_t::ANY_I8) {
        ret.i8 = islower((int)ss.i8);
    } else if (ss.type == atype_t::ANY_STRING) {
        int len = (int)strlen(ss.s);
        ret.i8 = 1;
        for (int i = 0; i < len; ++i) {
            if (!islower(ss.s[i])) {
                ret.i8 = 0;
                break;
            }
        }
    } else {
        mark_line();
        set_calc_error();
        do_error(&arg->loc, "try to apply islower on a non string type\n");
        return;
    } 
    env->clear();
    env->push_local(ret);
    env->return_done = true;
}
